package org.btik.lightdev.dev.tcp.auth;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.btik.light.meta.exception.LightException;
import org.btik.light.server.platform.common.api.dao.LightDao;
import org.btik.light.tool.BioUtil;
import org.btik.light.tool.ByteUtil;
import org.btik.light.tool.MDUtil;
import org.btik.light.tool.UuidUtil;
import org.btik.light.server.platform.common.api.bean.dev.access.DevAccount;


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * @author lustre
 * @version 1.0
 * @since 2021/5/15 12:37
 * 设备接入认证
 */
public class DevAuth implements AuthConstant {
    private static final Log log = LogFactory.getLog(DevAuth.class);
    private static final byte[] NONCE = "nonce".getBytes(StandardCharsets.UTF_8);

    private static final int NONE_LEN = NONCE.length;
    private int authTimeOut;

    private LightDao lightDao;

    public void setLightDao(LightDao lightDao) {
        this.lightDao = lightDao;
    }

    public void setAuthTimeOut(int authTimeOut) {
        this.authTimeOut = authTimeOut;
        log.info("auth time out is :" + authTimeOut);
    }

    public boolean isAuth(Socket sock) {
        try {
            InputStream inputStream = sock.getInputStream();
            // 设置登录认证时socket读取超时时间
            final int soTimeout = sock.getSoTimeout();
            sock.setSoTimeout(authTimeOut);

            // 检查是否为发送 请求nonce
            byte[] bytes = new byte[NONE_LEN];

            int len = inputStream.read(bytes);
            if (len < NONE_LEN) {
                if (log.isDebugEnabled()) {
                    log.error("not nonce request");
                }
                return false;
            }
            if (!Arrays.equals(NONCE, bytes)) {
                if (log.isDebugEnabled()) {
                    log.error("not nonce request");
                }
                return false;
            }
            // 返回nonce给设备
            byte[] nonce = UuidUtil.uuid(8);
            OutputStream outputStream = sock.getOutputStream();
            outputStream.write(nonce);
            outputStream.flush();
            log.info("send nonce");

            // 获取设备认证的用户名
            int userNameLen = inputStream.read();
            if (userNameLen < 0) {
                if (log.isDebugEnabled()) {
                    log.error("eof when read userNameLen");
                }
                return false;
            }
            byte[] username = BioUtil.read(inputStream, userNameLen);
            // 查询用户
            DevAccount devAccount = lightDao.get(new String(username).intern(), DevAccount.class);
            if (devAccount == null || !devAccount.getStatus()) {
                String usernameStr = new String(username).intern();
                log.error("user not exists :" + usernameStr);
                if (log.isDebugEnabled()) {
                    log.debug("userName:" + usernameStr);
                }
                return false;
            }
            // 根据用户密码哈希算法的哈希值长度读取设备提交的摘要
            MDUtil.Algorithm mdAlgorithm = devAccount.getMdAlgorithm();
            int mdLen = mdAlgorithm.getLength();
            // 计算摘要可能比较费时，这里设置两倍超时
            sock.setSoTimeout(authTimeOut * 2);
            byte[] md = BioUtil.read(inputStream, mdLen);
            // 利用服务端用户数据计算摘要，与设备摘要比较
            // 密码本身是账户加密码算出的哈希值。此处在之前加入nonce,便与设备一致
            byte[] serverMd = MDUtil.digest(mdAlgorithm, nonce, ByteUtil.hexStrToByteArray(devAccount.getPasswd()));
            if (log.isDebugEnabled()) {
                log.debug("nonce:" + ByteUtil.toHexString(nonce));
                log.debug("passwdHash:" + devAccount.getPasswd());
                log.debug("serverMd:" + ByteUtil.toHexString(serverMd));
            }
            if (Arrays.equals(md, serverMd)) {
                // 还原超时时间
                sock.setSoTimeout(soTimeout);
                return true;
            }
        } catch (IOException e) {
            log.warn("read cause", e);
            String message = e.getMessage();
            if ("Read timed out".equals(message)) {
                throw new LightException(message);
            }
            return false;
        }
        log.error("wrong user name or password!");
        return false;
    }
}
